package main;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import otherOrganizations.EAagent;
import otherOrganizations.MetOffice;
import otherOrganizations.ServiceCompany;
import otherOrganizations.Supplier;
import enviornment.Flood;
import enviornment.Location;
import enviornment.RelationshipEdge;
import agentsSME.ManufactureSME;
import agentsSME.Premise;
import agentsSME.SME;
import repast.simphony.context.Context;
import repast.simphony.context.space.graph.NetworkFactory;
import repast.simphony.context.space.graph.NetworkFactoryFinder;
import repast.simphony.dataLoader.ContextBuilder;
import repast.simphony.engine.schedule.Schedule;
import repast.simphony.space.graph.Network;
import repast.simphony.util.collections.IndexedIterable;

/**
 * Initialises the model.
 * @author Chunhui Li
 *
 */

public class BuildModel implements ContextBuilder<Object> {
	public static final int START_HOUR = 8; // the hour of the world time that the simulation clock starts
	public static final int START_WORK_HOUR = 9;
	public static final int END_WORK_HOUR = 18;
	public static final int TICK_PER_HOUR = 2; // how many ticks represent an hour in the simulation
	public static final int FLOOD_DATA_INTERVAL = 7;// the number of hours gap between 2 files
	public static final int DATA_SIZE = 91;//the number of ticks of flood data from hydrodynamic model 
	public static final int WORKINGHOURS = 9; // the number of working hours a day 
	
	
	public static final int NUMBER_SUBSCRIBER = 8;
	public static final double MICRO_SME=82.0; // Percentage of mirco SMEs in UK businesses
	public static final double SMALL_SME = 15.5;
	public static final double MEDIUM_SME= 2.5;
	public static final int RADIUS=100;
	public static final int ADDED_HOURS=24;
	
	public static int number_smes;
	
	public static final boolean FORCE_STOP = false;
	public static final double FORCE_STOP_TICK= 200.0;
	public static final double RETURN_RATE=0.8;
	
	public static final boolean FIXED_EXPERIMENT = true;
	
	public static final String FILE="testdata4.csv";
	
	private Random random= new Random();

	public static Context<Object> context;

	public static Context<Flood> floodContext;

	public static Context<SME> smeContext;

	private Network<Object> subPubNet;
	private Network<SME> smeCommunity;

	private EAagent ea;
	private Supplier supplier;

	public static Schedule schedule = new Schedule();
	private static Logger LOGGER = Logger.getLogger(BuildModel.class.getName());
	@Override
	public Context<Object> build(Context<Object> c) {

		try {
			SmeLogging.setup("Model_Logging.txt");
		} catch (IOException e1) {			
			e1.printStackTrace();
			throw new RuntimeException("Problems with creating the log files");

		}
		context = c;
		context.setId("simulation");

		//initialise the contexts.
		floodContext = new FloodContext();		
		smeContext = new SmeContext();

		context.addSubContext(floodContext);
		context.addSubContext(smeContext);

		// add Environment Agency agent
		ea = new EAagent();
		MetOffice metoffice = new MetOffice();
		context.add(ea);
		context.add(metoffice);
		supplier = new Supplier("supplier1", 60.0, 10, 20);
		context.add(supplier);

		//build network projections
		NetworkFactory networkFactory= NetworkFactoryFinder.createNetworkFactory(null);
		subPubNet= networkFactory.createNetwork("sub-pub", context, true);	
		smeCommunity = networkFactory.createNetwork("smeCommunity", smeContext, false);

		try {
			buildFloodAgents();
			buildSMEAgents();		
			
			customerizeSMEPopulation();
			initializeNeighbourhood();
			initialiseMutualAid();
			//	initializeContacts();
			//	printAgents();
		} catch (IOException e) {			
			e.printStackTrace();
		}
		System.out.println(context.size());
		System.out.println(" floodContext size= "+floodContext.size());
		System.out.println(" smeContext size= "+smeContext.size());
		number_smes=smeContext.size();
		return context;
	}

	/**
	 * This method is for debugging purpose.
	 */
	public  void printAgents(){
		System.out.println("Context size = "+context.size());
		IndexedIterable<Object> list= context.getObjects(Object.class);
		for(int i=0; i< context.size(); i++){	   
			System.out.println(list.get(i).toString());
		}

		System.out.println("floodContext size= "+floodContext.size());
		System.out.println("smeContext size= "+smeContext.size());
	}

	/**
	 * This method was created for schedules, but not in use by now (11/08/2014)
	 * @return
	 */
	public Schedule getSchedule(){
		return schedule;
	}

	/**
	 * Creates agents for the simulation.
	 * @throws IOException
	 */
	public void buildSMEAgents() throws IOException{		
		Random random = new Random();
		File dataFile = new File(FILE);		
		BufferedReader br;
		br = new BufferedReader(new FileReader(dataFile));
		String line;
		
		LOGGER.log(Level.INFO, "Configuring the environment with data from the csv file ");
		String tempo=" ";
		ServiceCompany service=new ServiceCompany(20, "s1");
		context.add(service);
		while ((line = br.readLine()) != null) {
			// process the line.	

			String[] split = line.split(",");
			String id = split[0];
			String x=split[1];
			String y= split[2];
					
			if(!id.equals(tempo)){	
				Location loc = new Location(Integer.parseInt(x),Integer.parseInt(y));
				Premise premise = new Premise(random.nextInt(2)+1, loc, random.nextInt(2)+1);	
				ManufactureSME sme = new ManufactureSME();
				sme.setPremise(premise);
				sme.setLocation(loc);
				sme.setSerialNo(split[0]);
				sme.setCompanyName(split[3].trim());
			//	sme.setCapability(drawRandomCapability());				
				sme.setBusinessCode(split[6]);
				sme.addServiceContact(service);				
				smeContext.add(sme);
				supplier.addCustomers(sme);
			//	System.out.println("Created a sme agent "+ sme.getName());
//				String msg="Created a sme agent "+ sme.toString();
//				LOGGER.log(Level.INFO, msg);
			}
						
			tempo=id;
		}		
		br.close();
		
	}
	public void buildFloodAgents() throws IOException{
		File dataFile = new File(FILE);	
//		double[] building = new double [DATA_SIZE];
//		double[] road = new double[DATA_SIZE];
		BufferedReader br;
		br = new BufferedReader(new FileReader(dataFile));
		String line;
		LinkedList<Flood> list=new LinkedList<Flood>();
		LOGGER.log(Level.INFO, "Configuring the environment with data from the csv file ");
		String tempo=" ";
		while ((line = br.readLine()) != null) {
			// process the line.			
			String[] split = line.split(",");
			String id = split[0];
			String x=split[1];
			String y= split[2];
			int tick= Integer.parseInt(split[4]);
						
			if(!id.equals(tempo)){	
				Location loc = new Location(Integer.parseInt(x),Integer.parseInt(y));
				Flood flood=new Flood(loc);
				flood.setID(id);
				flood.setBuildingData(tick, Double.parseDouble(split[6]));
				flood.setRoadData(tick, Double.parseDouble(split[7]));
				list.add(flood);
//				String msg="Created a Flood agent "+ flood.toString();
//				LOGGER.log(Level.INFO, msg);
			} 
			if(id.equals(tempo)){
				list.getLast().setBuildingData(tick, Double.parseDouble(split[6]));
				list.getLast().setRoadData(tick, Double.parseDouble(split[7]));
//			    String msg="SET flood data tick "+tick+"   "+ split[6]+"     "+split[7];
//				LOGGER.log(Level.INFO, msg);
			}
			tempo=id;
		}		
		br.close();
		for(Flood f: list){			
			f.checkNightFlood();
			floodContext.add(f);
		}	
	}
	/*
	public String drawRandomCapability(){
		String[] list={"A", "B"};
		return list[random.nextInt(list.length)];
	}*/
	public void customerizeSMEPopulation(){
		if(!FIXED_EXPERIMENT){
			int micro = (int)(smeContext.size() * MICRO_SME/100);
			int small = (int)(smeContext.size() * SMALL_SME/100);
	//		int medium = smeContext.size()-micro-small;
			int x=0;
			for (SME sme: smeContext){
				x++;
				if(x<=micro){
					sme.setSize(random.nextInt(9)+1);
				}
				if(x>micro&& x<=micro+small){
					sme.setSize(random.nextInt(40)+10);
					
				}
				if(x>micro+small){
					sme.setSize(random.nextInt(200)+50);
					
				}
				int number= sme.getSize()-(int)Math.floor(sme.getSize()* BuildModel.RETURN_RATE);
				if(number>0){
					sme.setAbsentees(number);
				}else{
					sme.setAbsentees(1);
				}
				
				System.out.println(sme.toString());
			}
		}
		if(FIXED_EXPERIMENT){
			int x=0;
			for (SME sme: smeContext){
				sme.setSize(10);
				sme.setAbsentees(2);
				if(x<2){
					sme.setCapability("A");					
				}else{
					sme.setCapability("B");
				}
				x++;
				System.out.println(sme.toString());
			}
		}
		
		
	}
	public void initialiseMutualAid(){
	/*
		SME s = smeContext.getRandomObject();
		for (SME sme: smeContext){
			if(s.getCapability().equals(sme.getCapability()) && !s.getName().equals(sme.getName())){
				s.makeMutualAidAgreementWith(sme);
				s.setParticipation(true);
				sme.makeMutualAidAgreementWith(s);
				sme.setParticipation(true);
			}
		}*/
		LinkedList<SME> list=new LinkedList<SME>();
		for (SME t: smeContext){
			list.add(t);
		}
			
	    int i=0;
		for (SME sme: list){
			i++;
			System.out.println(i+"  "+sme.getName()+"  "+sme.getCapability());
			if(!sme.getParticipation()){
				for(SME t:list)	{	
					
					if(t.getName().compareTo(sme.getName())!=0){
						System.out.println(t.getName().compareTo(sme.getName())+"  "+t.getName()+"  "+t.getCapability());					
						if(sme.getCapability().equals(t.getCapability())){
						System.out.println(t.getName()+"  and   "+ sme.getName());
							t.makeMutualAidAgreementWith(sme);
							t.setParticipation(true);
							sme.makeMutualAidAgreementWith(t);
							sme.setParticipation(true);
							
						}
					}
				}
				
			}
			System.out.println(sme.toString());
		}
		
		
		
	}
	

	/**
	 * Finds the SMEs which have the same location and adds edges among them to mark neighbours.
	 */
	public void initializeNeighbourhood(){
		// build neighbourhood according to the location
		LOGGER.log(Level.INFO, "-------Initialize neighbourhood-------");
		IndexedIterable<SME> list= smeContext.getObjects(Object.class);
		for(int i=0; i< smeContext.size(); i++){
			SME s1 = list.get(i);
			for(int j= i+1; j<smeContext.size(); j++){
				SME s2 = list.get(j);
				if (matchedLocation(s1.getLocation(),s2.getLocation()) && !s2.getName().contains(s1.getName())){
					s1.addNewNeighbour(s2);
					s2.addNewNeighbour(s1);
					System.out.println("Found "+s2.getName()+" has the same location as "+ s1.getName() );
				//	String msg="Found "+s2.getName()+" has the same location as "+ s1.getName() ;
				//	LOGGER.log(Level.INFO, msg);
					RelationshipEdge<SME> edge = new RelationshipEdge<SME>(s1, s2, false);
					smeCommunity.addEdge(edge);
				}

			}

		}
		// add random sme to the subscriber list of EA warnings
		LOGGER.log(Level.INFO, "---- ADD SMES TO THE LIST OF SUBSCRIBER OF WARNINGS ----");
		for (int i=0; i<NUMBER_SUBSCRIBER ; i++){
			SME s = smeContext.getRandomObject();
			String msg = s.getName()+" is added to the subscriber list:";
			LOGGER.log(Level.INFO, msg);
			ea.getSender().addSubscriber(s);
			s.setSubscriber(true);
		//	System.out.println(s.toString());
		}
	}

	/**
	 * Checks whether two locations have the same coordinates.
	 * @param x Location 1
	 * @param y Location 2
	 * @return true if 2 locations are matched.
	 */
	public boolean matchedLocation(Location x,Location y){
		boolean matched = false;
		if((y.getLocationX()<=x.getLocationX()+RADIUS && y.getLocationX()>=x.getLocationX()-RADIUS) &&
				(y.getLocationY()>= x.getLocationY()-RADIUS && y.getLocationY()<=x.getLocationY()+RADIUS)){
			matched = true;
		}
		return matched;
	}

	/**
	 * Initialises the contact list of each SME and add a new edge when a contact is added.
	 */
	public void initializeContacts(){
		if (smeContext.iterator().hasNext()){
			SME s =smeContext.iterator().next();
			if (s.getNeighbours()!=null){
				s.getContacts().initialise();
			}
		}
	}

}

